class Graphics {
  html.CanvasElement canvas;
  html.WebGLRenderingContext gl;
  html.WebGLBuffer positionsVB;
  html.WebGLBuffer texCoordsVB;
  html.WebGLBuffer ColorsVB;
  mat4 worldTM; 
  mat4 projectionTM;
  num aspect;
  num fov;
  num cameraDistance;
  
  Graphics(html.CanvasElement canvas) {    
    var glNames = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl'];
    var options = {'alpha': false, 'depth': false, 'stencil': false};

    for (final item in glNames) {
      try {
        gl = canvas.getContext(item);
      } catch (final Exception e) { }
      
      if (gl != null)
        break;
    }
    
    if (gl == null)
      throw new UnsupportedOperationException('start failed. WebGL not initialized.');

    gl.clearColor(0.392, 0.584, 0.929, 1.0);
    gl.disable(html.WebGLRenderingContext.CULL_FACE);
    gl.disable(html.WebGLRenderingContext.DEPTH_TEST);
    gl.viewport(0, 0, canvas.width, canvas.height);
    
    this.canvas = canvas;
    this.aspect = canvas.width / canvas.height;
    this.fov = Math.PI * 0.25;
    this.cameraDistance = Math.tan((Math.PI - fov) * 0.5) * canvas.width * 0.5 / aspect;
    positionsVB = gl.createBuffer();
    texCoordsVB = gl.createBuffer();
    ColorsVB = gl.createBuffer();
    
    Texture._graphics = this;
    GPUProgram._graphics = this;
  }
  
  void begin() {   
    worldTM = new mat4.identity();
    projectionTM = new mat4.perspective(fov, aspect, 0.1, 10000.0);
    projectionTM.translate3D(0, 0, -cameraDistance);
    projectionTM *= new mat4.rotationX(Math.PI);
    projectionTM.translate2D(-canvas.width * 0.5, -canvas.height * 0.5);
    gl.clear(html.WebGLRenderingContext.COLOR_BUFFER_BIT);
  }
  
  void end() {
    gl.flush();
  }
  
  void draw(GPUProgram program, Texture texture, num x, num y, num originX, num originY, List<int> sourceRectangle) {
    if (texture == null || texture.status != Asset.LOADED)
      return;
    
    x -= originX;
    y -= originY;
    
    num w = sourceRectangle[2];
    num h = sourceRectangle[3];
    num tw = texture.width;
    num th = texture.height;
    num sl = sourceRectangle[0];
    num st = sourceRectangle[1];
    num sr = sl + w;
    num sb = sl + h;
    var pos = [x,     y, 0,
               x + w, y, 0,
               x,     y + h, 0,
               x + w, y + h, 0];
    var uv = [sl / tw, 1.0 - st / th,
              sr / tw, 1.0 - st / th,
              sl / tw, 1.0 - sb / th,
              sr / tw, 1.0 - sb / th];
    
    gl.enable(html.WebGLRenderingContext.BLEND);
    gl.blendFunc(html.WebGLRenderingContext.SRC_ALPHA, html.WebGLRenderingContext.ONE_MINUS_SRC_ALPHA);
    gl.blendEquation(html.WebGLRenderingContext.FUNC_ADD);

    gl.useProgram(program.handle);
    gl.enableVertexAttribArray(program.VPosition);
    gl.enableVertexAttribArray(program.VTexture1);
    
    gl.bindBuffer(html.WebGLRenderingContext.ARRAY_BUFFER, positionsVB);
    gl.bufferData(html.WebGLRenderingContext.ARRAY_BUFFER, new html.Float32Array.fromList(pos), html.WebGLRenderingContext.DYNAMIC_DRAW);
    gl.vertexAttribPointer(program.VPosition, 3, html.WebGLRenderingContext.FLOAT, false, 0, 0);
    
    gl.bindBuffer(html.WebGLRenderingContext.ARRAY_BUFFER, texCoordsVB);
    gl.bufferData(html.WebGLRenderingContext.ARRAY_BUFFER, new html.Float32Array.fromList(uv), html.WebGLRenderingContext.DYNAMIC_DRAW);
    gl.vertexAttribPointer(program.VTexture1, 2, html.WebGLRenderingContext.FLOAT, false, 0, 0);
    
    gl.activeTexture(html.WebGLRenderingContext.TEXTURE0);
    gl.bindTexture(html.WebGLRenderingContext.TEXTURE_2D, texture.handle);
    gl.uniform1f(program.Sampler1, 0);
    
    gl.uniformMatrix4fv(program.World, false, new html.Float32Array.fromList(worldTM.m));
    gl.uniformMatrix4fv(program.Projection, false, new html.Float32Array.fromList(projectionTM.m));
    gl.drawArrays(html.WebGLRenderingContext.TRIANGLE_STRIP, 0, 4);
    //gl.bindBuffer(html.WebGLRenderingContext.ARRAY_BUFFER, null);
  }
}
